
/**
  ******************************************************************************
  * Copyright 2021 The Microbee Authors. All Rights Reserved.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * 
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  * 
  * @file       task_serial.c
  * @author     baiyang
  * @date       2021-7-15
  ******************************************************************************
  */

/*----------------------------------include-----------------------------------*/
#include "task_serial.h"

#include <rthw.h>

#include <common/microbee.h>
#include <common/console/console.h>
#include <parameter/param.h>
#include <common/time/gp_time.h>
#include <board_config/board_config.h>
/*-----------------------------------macro------------------------------------*/
#define SERIAL_SEND_INTERVAL 5
/*----------------------------------typedef-----------------------------------*/
struct serial_thread_resource
{
    char thread_serial_task_stack[512];
    struct rt_thread thread_serial_task_handle;
};
/*---------------------------------prototype----------------------------------*/

/*----------------------------------variable----------------------------------*/
struct gp_serial_thread serial[GP_SERIAL_COUNT_MAX];

struct serial_thread_resource serial_resource[GP_SERIAL_COUNT_MAX];

// 串口通信用线程
static char thread_serial_rx_task_stack[512];
struct rt_thread thread_serial_rx_task_handle;

perf_counter_t _serial_rx_elapsed_perf;
perf_counter_t _serial_rx_interval_perf;
/*-------------------------------------os-------------------------------------*/

/*----------------------------------function----------------------------------*/
/**
  * @brief       UART发送完成回调,DMA发送完成释放信号量
  * @param[in]   dev  
  * @param[in]   buffer  
  * @param[out]  
  * @retval      
  * @note        
  */
static rt_err_t _uart_tx_done(rt_device_t dev, void* buffer)
{
    for(uint8_t i = 0; i < 8; i++)
    {
        if(serial[i].sdevice != RT_NULL)
        {
            if(serial[i].sdevice->puart == dev)
            {
                return rt_sem_release(serial[i].tx_sem);
            }
        }
    }
    
    RT_ASSERT(0);
    return RT_FALSE;
}

/**
  * @brief       循环开始前的初始化
  * @param[in]   thread  
  * @param[out]  
  * @retval      
  * @note        
  */
static void serial_thread_init(gp_serial_thread_t thread)
{
    thread->res = 0;

    RT_ASSERT(thread->sdevice != RT_NULL);
    RT_ASSERT(thread->sdevice->puart != RT_NULL);

    if(thread->sdevice->oflag & RT_DEVICE_FLAG_DMA_TX) //DMA模式下需要信号量触发回调
    {
        thread->tx_sem = rt_sem_create(thread->sem_name, 0, RT_IPC_FLAG_FIFO);

        if (!thread->tx_sem) {
            brd_config_error("function:%s, line number:%d, tx_sem is NULL\n",__FUNCTION__, __LINE__);
        }

        rt_device_set_tx_complete(thread->sdevice->puart, _uart_tx_done);
    }
}

// record timestamp of new incoming data
static inline void receive_timestamp_update(gp_serial_thread_t thread)
{
    thread->sdevice->receive_timestamp[thread->sdevice->receive_timestamp_idx^1] = time_micros64();
    thread->sdevice->receive_timestamp_idx ^= 1;
}

/**
  * @brief       循环接收
  * @param[in]   thread  
  * @param[out]  
  * @retval      
  * @note        
  */
static bool serial_rx_loop(gp_serial_thread_t thread)
{
    if (thread == NULL || thread->sdevice == NULL || thread->sdevice->puart == NULL) {
        return false;
    }

    rt_size_t size = 0;

    //从UART缓冲区读取数据,并写入SERIAL缓冲区rb_rx
    uint8_t rec_data[128];

    while(true)
    {
        rt_size_t rec_data_size = rt_device_read(thread->sdevice->puart, 0, rec_data, sizeof(rec_data));

        if (rec_data_size == 0) {
            break;
        }

        SERIAL_ENTER_CRITICAL;
        rt_ringbuffer_put_force(thread->sdevice->rb_rx,rec_data,rec_data_size);
        SERIAL_EXIT_CRITICAL;

        receive_timestamp_update(thread);

        size += rec_data_size;
    }

    if (size > 0) {
        if(thread->sdevice->parent.rx_indicate != RT_NULL)
        {
            thread->sdevice->parent.rx_indicate(&thread->sdevice->parent, rt_ringbuffer_data_len(thread->sdevice->rb_rx)); //如果上次数据读完，进入接受完成回调
        }
    }

    return true;
}

/**
  * @brief       循环发送
  * @param[in]   thread  
  * @param[out]  
  * @retval      
  * @note        
  */
static void serial_tx_loop(gp_serial_thread_t thread)
{
    uint8_t send_data[128];
    uint16_t send_len = 0;

    while (true) {
        // 从SERIAL缓冲区rb_tx读取数据,使用UART驱动发送
        SERIAL_ENTER_CRITICAL;

        send_len = rt_ringbuffer_data_len(thread->sdevice->rb_tx);

        if (send_len > 0) {
            uint16_t len = send_len > sizeof(send_data) ? sizeof(send_data) : send_len;

            rt_ringbuffer_get(thread->sdevice->rb_tx, send_data, len);

            SERIAL_EXIT_CRITICAL;

            rt_device_write(thread->sdevice->puart, 0, send_data, len);

            if (thread->sdevice->parent.open_flag & RT_DEVICE_FLAG_INT_TX) {
                if(thread->sdevice->parent.tx_complete != RT_NULL) {
                    thread->sdevice->parent.tx_complete(&thread->sdevice->parent, (void *)send_data); //发送完成回调，测试没问题,目前在event_task线程中测试
                }
            }
#ifdef RT_SERIAL_USING_DMA
            else if (thread->sdevice->parent.open_flag & RT_DEVICE_FLAG_DMA_TX) {
                rt_sem_take(thread->tx_sem, RT_WAITING_FOREVER); //等待信号量（表示DMA发送完成）

                if(thread->sdevice->parent.tx_complete != RT_NULL) {
                    thread->sdevice->parent.tx_complete(&thread->sdevice->parent, (void *)send_data); //发送完成回调，测试没问题,目前在event_task线程中测试
                }
            }
#endif /* RT_SERIAL_USING_DMA */

        }else {
            /* enable interrupt */
            SERIAL_EXIT_CRITICAL;
            break;
        }
    }
}

/*----------------------------------serial rx--------------------------------*/

static void serial_init()
{
    serial[0].name = "serial0";
    serial[0].sem_name = "s0_sem";
    serial[0].sdevice = (gp_serial_device_t)rt_device_find(serial[0].name);  //查找设备

#if (GP_SERIAL_COUNT_MAX > 1)
    serial[1].name = "serial1";
    serial[1].sem_name = "s1_sem";
    serial[1].sdevice = (gp_serial_device_t)rt_device_find(serial[1].name);  //查找设备
#endif

#if (GP_SERIAL_COUNT_MAX > 2)
    serial[2].name = "serial2";
    serial[2].sem_name = "s2_sem";
    serial[2].sdevice = (gp_serial_device_t)rt_device_find(serial[2].name);  //查找设备
#endif

#if (GP_SERIAL_COUNT_MAX > 3)
    serial[3].name = "serial3";
    serial[3].sem_name = "s3_sem";
    serial[3].sdevice = (gp_serial_device_t)rt_device_find(serial[3].name);  //查找设备
#endif

#if (GP_SERIAL_COUNT_MAX > 4)
    serial[4].name = "serial4";
    serial[4].sem_name = "s4_sem";
    serial[4].sdevice = (gp_serial_device_t)rt_device_find(serial[4].name);  //查找设备
#endif

#if (GP_SERIAL_COUNT_MAX > 5)
    serial[5].name = "serial5";
    serial[5].sem_name = "s5_sem";
    serial[5].sdevice = (gp_serial_device_t)rt_device_find(serial[5].name);  //查找设备
#endif

#if (GP_SERIAL_COUNT_MAX > 6)
    serial[6].name = "serial6";
    serial[6].sem_name = "s6_sem";
    serial[6].sdevice = (gp_serial_device_t)rt_device_find(serial[6].name);  //查找设备
#endif

#if (GP_SERIAL_COUNT_MAX > 7)
    serial[7].name = "serial7";
    serial[7].sem_name = "s7_sem";
    serial[7].sdevice = (gp_serial_device_t)rt_device_find(serial[7].name);  //查找设备
#endif

#if (GP_SERIAL_COUNT_MAX > 8)
    serial[8].name = "serial8";
    serial[8].sem_name = "s8_sem";
    serial[8].sdevice = (gp_serial_device_t)rt_device_find(serial[8].name);  //查找设备
#endif
}

/*---------------------------------serial rx---------------------------------*/
/**
  * @brief       
  * @param[in]   parameter  
  * @param[out]  
  * @retval      
  * @note        
  */
void serial_rx_main(void *parameter)
{
    while(1)
    {
        perf_count(_serial_rx_interval_perf);

        perf_begin(_serial_rx_elapsed_perf);

        for (uint8_t i=0; i<GP_SERIAL_COUNT_MAX; i++) {
            if (serial[i].sdevice == NULL || serial[i].sdevice->protocol == SerialProtocol_None) {
                continue;
            }

            serial_rx_loop(&serial[i]);
        }

        perf_end(_serial_rx_elapsed_perf);

        rt_thread_mdelay(1);
    }
}

/*---------------------------------serial tx---------------------------------*/
/**
  * @brief       serial发送线程主函数
  * @param[in]   parameter  
  * @param[out]  
  * @retval      
  * @note        
  */
void serial_tx_main(void *parameter)
{
    gp_serial_thread_t serial_thread = (gp_serial_thread_t)parameter;
    rt_device_t serial_dev = rt_device_find(serial_thread->name);

    if(!(serial_thread->sdevice != RT_NULL
        && serial_thread->sdevice->protocol != SerialProtocol_None
        && serial_dev != RT_NULL))
    {
        return;
    }

    serial_thread_init(serial_thread);

    while(1)
    {
        perf_count(serial_thread->_serial_tx_interval_perf);

        serial_tx_loop(serial_thread);
        rt_thread_mdelay(SERIAL_SEND_INTERVAL);
    }
}

/*---------------------------------serial init---------------------------------*/
rt_err_t task_serial_init(void)
{
    rt_err_t res;

    gp_serial_device_init();

    serial_init();

    _serial_rx_elapsed_perf = perf_alloc(PC_ELAPSED, "uart_rx: ET");
    _serial_rx_interval_perf = perf_alloc(PC_INTERVAL, "uart_rx: INR");

    res = rt_thread_init(&thread_serial_rx_task_handle,
                           "serialrx",
                           serial_rx_main,
                           RT_NULL,
                           &thread_serial_rx_task_stack[0],
                           sizeof(thread_serial_rx_task_stack),PRIORITY_UART_RX,5);
    if (res == RT_EOK) {
        rt_thread_startup(&thread_serial_rx_task_handle);
    }

    struct serial_thread_resource *resource;
    gp_serial_thread_t serial_thread;
    char thread_name[RT_NAME_MAX];

    for (uint8_t i = 0; i < GP_SERIAL_COUNT_MAX; i++) {
        resource = &serial_resource[i];
        serial_thread = &serial[i];

        if (serial_thread->sdevice != RT_NULL) {
            rt_snprintf(thread_name, sizeof(thread_name), "serial%u",i);

            serial_thread->_serial_tx_interval_perf = perf_alloc(PC_INTERVAL, serial_thread->name);

            res = rt_thread_init(&resource->thread_serial_task_handle,
                               thread_name,
                               serial_tx_main,
                               serial_thread,
                               resource->thread_serial_task_stack,
                               sizeof(resource->thread_serial_task_stack),PRIORITY_UART_TX,1);
            if (res == RT_EOK) {
                rt_thread_startup(&resource->thread_serial_task_handle);
            }
        }
    }

    return RT_EOK;
}

/*------------------------------------test------------------------------------*/


